Arquitetura Interna

Modularidade

A biblioteca sem os diversos drivers, é composta por poucos módulos. os arquivos de inclusão públicos cd.h e wd.h, e os arquivos que implementam essas funções independentes de drivers cd.c e wd.c, e o arquivo de inclusão cdprivat.h. Além de alguns outros módulos que implementam funções específicas não exportadas. Estes módulos são completamente independentes dos drivers implementados. Assim como, cada um dos drivers é independente do outro, a não ser que haja uma dependência proposital.

Amarração

Como os drivers são independentes entre sí poderíamos criar uma biblioteca para cada um deles, para os drivers providos com a biblioteca CD foi mais fácil incluí-los na própria biblioteca, simplificando o processo de amarração (link) da aplicação. Observação: internamente drivers são chamados de contexto (Context).

Para criar essa independência, o usuário ao criar um canvas de um determinado driver precisa especificar qual o driver a ser utilizado. A especificação é feita atravéz de uma macro que na realidade é uma função sem parâmetro algum que passa para a função de criação do canvas a tabela de funções daquele driver. Por exemplo:

CD_PS => cdContext* cdContextPS();
cdCreateCanvas(CD_PS, "teste.ps"); => novo_canvas->cdLine = context->cdLine

Como cada primitiva é chamada sem o canvas como parâmetro, assume-se um canvas ativo, ou seja depois de criado, o canvas precisa ser ativado para poder ser usado. Com isso temos que manter internamente um objeto contendo o canvas ativo.

Quando uma função é chamada, é então chamada a função do canvas ativo que na realidade é a função do driver de onde foi criado aquele canvas. Por exemplo:

cdLine => canvas_ativo->cdLine => context->cdLine

Estruturas

A parte de controle possue apenas 3 estruturas. Duas são estruturas privadas respectivas as estruturas públicas do cd.h, cdPrivateContext e cdPrivateCanvas. A outras estrutura é apenas interna usada para as funções WD, wdCanvas. A estrutura cdPrivateContext é usada somente pelos drivers. A estrutura cdPrivateCanvas contém a tabela de funções e os valores de todos os atributos que podem ser consultados.

Os drivers também não precisam implementar todas as funções da tabela de funções, pois as que não estão implementadas, por não fazerem sentido naquele driver, simplesmente não serão executadas sem gerar um erro para o usuário da biblioteca.

Cada um dos drivers possui uma estrutura interna de canvas para sí. Além dessa estrutura os drivers tem que definir a estrutura cdContext a ser retornada pela função cdContextXX() (onde XX varia de acordo com o driver, como vimos anteriormente). Com isso os drivers tem que definir também uma estrutura cdPrivateContext para ser incluída na estrutura cdContext do mesmo.

A tabela abaixo ilustra esse mecanismo:
 

typedef struct _cdContext
{
  void *ctx;   =============>
} cdContext;
typedef struct _cdPrivateContext
{
  void* (*CreateCanvas)(cdCanvas* canvas, void *data);
  int  (*Play)(int xmin, int xmax, int ymin, int ymax, void *data);
  int  (*RegisterCallback)(int cb, cdCallback func);
} cdPrivateContext;
typedef struct _cdCanvas
{
  void *cnv;   =============>
} cdCanvas;
typedef struct _cdPrivateCanvas
{
  ...
  void (*Line)(int x1, int y1, int x2, int y2);
  void (*Rect)(int xmin, int xmax, int ymin, int ymax);
  void (*Box)(int xmin, int xmax, int ymin, int ymax);
  ...

  ...
  int mark_type, mark_size;
  int line_style, line_width;
  int interior_style, hatch_style;
  ...

  void* wd_canvas;       =============>  wdCanvas
  void* context_canvas;  =============>  cdCanvasXX
  void* context;         =============>  cdContext
} cdPrivateCanvas;

 

Para simplificar o gerenciamento dos drivers, a amarração das estruturas de contexto é feita da seguinte forma:

/* No arquivo de inclusão */
#define CD_METAFILE cdContextMetafile()
cdContext* cdContextMetafile(void)


/* No arquivo de implementação */
static cdPrivateContext private_context_metafile =
{
  cdMFcreatecanvas,
  cdMFplay,
  cdMFregistercallback
};

static cdContext context_metafile =
{
  &private_context_metafile
};

cdContext* cdContextMetafile(void)
{
  return &context_metafile;
}

Em CDLUA, para criar um novo driver deve-se usar a função interna cdluaAddContext. Todos os drivers pré-definidos são incluidos também dessa forma. Cria-se uma estrutura estática cdContextLUA com as informações necessárias e a função de inicialização do driver, como em cdluaiup_open, chama a função cdluaAddContext e registra valores se necessário. Veja como exemplo o arquivo CDLUAMF.C que ilustra esse processo.

Atributos

O mecanismo de query de um atributo é feito ainda na parte de controle e não chega até o driver, ou seja, os drivers não precisam se preocupar com o mecanismo de query e podem consultar a cdPrivateCanvas relativa ao canvas ativo a qualquer instante.  Aproveitando isso, atributos que são modificados repetidas vezes para o mesmo valor, não são atualizados nos drivers, poupando processamento. Assim como, se um atributo modificado em um driver não teve uma modificação bem sucedida, seu valor não é atualizado. Mas o fato de um driver não implementar a função de modificação do atributo, não significa que o driver rejeita aquele atributo, ele apenas não precisa fazer nada com o atributo naquele momento, ele vai consultar o atributo posteriormente antes de desenhar a primitiva.

A criação de atributos personalizados para cada driver é feita de forma genérica usando atributos do tipo string. Deve ser declarada uma estrutura com o nome do atributo e suas funções de set e get, como no exemplo abaixo:

static void set_fill_attrib(char* data)
{
  CurrentCanvas->fill_attrib[0] = data[0];
}

static char* get_fill_attrib(void)
{
  return CurrentCanvas->fill_attrib;
}

static cdAttribute fill_attrib =
{
  "SIMPENFILLPOLY",
  set_fill_attrib,
  get_fill_attrib
}; 

Na createcanvas do driver deve existir, por exemplo:

new_canvas->fill_attrib[0] = '1';
new_canvas->fill_attrib[1] = 0;

cdRegisterAttribute(private_canvas, &fill_attrib);

Inicializando o atributo e registrando-o na lista de atributos do canvas.

Nomenclatura

Todas as funções, variáveis ou enumerações que são públicas, direta ou indiretamente, possuem o prefixo "CD" ("cd" para funções e variáveis, e "CD_" para enumerações). No caso dos drivers deve-se acrescentar após ao prefixo uma identificação de uma ou duas letras, relativa ao driver (ex: "cdps" para o driver Postscript). Os módulos seguem esta mesma regra, inclusive módulos de diferentes plataformas recebem um acrescimo no prefixo indicando a plataforma (ex: "cdwiup", driver IUP em Windows), isso ajuda a resolver conflitos para o programa de controle de versão RCS.

Não existem variáveis globais na parte de controle, mas alguns drivers por razões históricas podem conter algumas variáveis globais, de qualquer forma elas procuram seguir a nomenclatura definida e não devem dar problemas..

Histórico

A primeira versão da biblioteca utilizava macros para substituir os nomes das funções por ponteiros em uma estrutura de ponteiros de funções, ou seja uma tabela de funções que indiretamente era visível ao usuário da biblioteca. Isso gerava vários problemas, principalmente com o uso da biblioteca dinâmica e do fato que não poderíamos controlar de maneira alguma o caso de um canvas não estar ativo. Também indiretamente os usuários dependiam do arquivo de inclusão cdprivat.h.

Decidimos então esconder a tabela de funções criando uma camada intermediaria entre as funções públicas e a tabela de funções. Mas os drivers ainda conhecem a estrutura da tabela, isso faz com que uma mudança na tabela implique em mudar todos os drivers.

Assim, mais uma vez a estrutura da biblioteca foi modificada para que os drivers ficassem independentes da tabela de funções. Com essa modificação podemos montar um esquema para que possamos substituir qualquer primitiva de um determinado driver pela primitiva do driver de simulação. Assim como, permitir que o usuário modifique uma das primitivas de um driver para a primitiva que ele especificar.

Aproveitando essa mudança atacamos também um problema referente as funções WD que eram necessariamente clientes dos drivers atuais. Isso gerava algumas perdas de precisão e conversões desnecessárias. Incluindo as funções WD na tabela de funções e usando as funções WD já implementadas como funções default para os drivers onde não existam funções WD, corrigimos esse problema porque onde pode-se implementar as funções as primitivas são executadas com melhor precisão e também podem ser chamadas a partir da função cdPlay ao interpretar um metafile.